package com.panopset.net.ftp;

import static com.panopset.Util.*;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class Operation {

    public Operation(Server server) throws IOException {
        this.server = server;
    }

    /**
     * If a connection is lost, the program will try to
     * re-connect.  To avoid an infinite re-try loop that
     * accomplishes nothing, this is the maximum number of
     * retries.  The maximumRetries public static variable
     * can safely be set at any time.  The default initial
     * value is 10.
     */
    public int maxRetries = 10;
    /**
     * Default is false, set to true if you want to skip existing files.
     */
    public boolean skipExisting = false;
    /**
     * Default is false.  Set to true if you want to
     * drill in to sub-directories.  Pass
     * "DRILL" as a parameter to set this to true.
     */
    public boolean drill = false;
    private String localPath;
    private final Server server;

    /**
     * Execute FTP operation
     *
     * @param s array
     * @throws IOException
     */
    public void getFile() throws IOException {
        final File clientFile = new File(localPath);

        server.getFile(clientFile);
        close();
    }

    /**
     * Execute FTP operation
     *
     * @param s array
     * @throws IOException
     */
    public final void putFile() throws IOException {

        final File clientFile = new File(localPath);

        int retries = 0;
        while (retries++ < maxRetries) {
            try {
                if (clientFile.isDirectory()) {
                    for (File f : clientFile.listFiles()) {
                        put(f);
                    }
                } else {
                    put(clientFile);
                }
                retries = maxRetries;
            } catch(IOException ex) {
                error(ex);
                info("Trying again, retry #" + retries + " of " + maxRetries);
                server.close();
                server.connect();
            }
        }
        close();
    }

    String getResponse(String command) throws IOException {
        return server.sendCommand(command);
    }

    private final void put(File file) throws IOException {
        if (file.isDirectory()) {
            if (drill) {
                server.cwd(file.getName());
                for (File f : file.listFiles()) {
                    put(f);
                }
                server.cwd("..");
            }
        } else {
            if (skipExisting) {
                String response = getResponse("size " + file.getName());
                if (response != null && response.indexOf("500") == -1) {
                    info("File exists, skipping: " + file.getName());
                }
            }
            info("transferring " + file.getCanonicalPath());
            stor(file);
        }
    }

    public void disconnect() throws IOException {
        try {
            sendCommand("QUIT");
        } finally {
            server.close();
        }
    }

    public String pwd() throws IOException {
        String response = sendCommand("PWD");
        String dir = null;
        if (response.startsWith("257 ")) {
            int firstQuote = response.indexOf('\"');
            int secondQuote = response.indexOf('\"', firstQuote + 1);
            if (secondQuote > 0) {
                dir = response.substring(firstQuote + 1, secondQuote);
            }
        }
        return dir;
    }

    public synchronized boolean stor(File file) throws IOException {
        if (file.isDirectory()) {
            throw new IOException("FtpOp cannot upload a directory.");
        }

        String filename = file.getName();

        return server.stor(new BufferedInputStream(new FileInputStream(file)), filename);
    }

    /**
     * Enter ASCII mode for sending text files. This is usually the default
     * mode. Make sure you use binary mode if you are sending images or
     * other binary data, as ASCII mode is likely to corrupt them.
     */
    public synchronized boolean ascii() throws IOException {
        return (sendCommand("TYPE A").startsWith("200 "));
    }

    public String sendCommand(String command) throws IOException {
        return server.sendCommand(command);
    }

    private static final int FILE_LIST_START_POS = 39;

    public boolean retrDir(String name) throws IOException {
        File toFile = new File(localPath + "/" + name);
        mkdirs(toFile);

        server.cwd(name);

        for (String s : server.list()) {
            info(s);
            if (s.length() > FILE_LIST_START_POS) {
                String fn = s.substring(FILE_LIST_START_POS);
                info("Getting file: " + fn);
                server.getFile(new File(toFile.getCanonicalPath() + "/" + fn));
            } else {
                info("Ignoring line as too short: " + s);
            }
        }
        return false;
    }

    public synchronized void close() throws IOException {
        server.close();
    }

    public static class Builder {

        private String siteName;
        private String remotePath;
        private String localPath;
        private String user;
        private String password;
        private Integer maxRetries;
        private boolean skipExisting = false;
        private boolean drill = false;

        public Operation construct() throws IOException {
            Operation rtn = new Operation(new Server(siteName, remotePath, user, password));

            if (localPath != null) {
                rtn.setLocalPath(localPath);
            }

            if (maxRetries != null) {
                rtn.maxRetries = maxRetries;
            }
            rtn.drill = drill;
            rtn.skipExisting = skipExisting;
            return rtn;
        }

        public Builder siteName(String siteName) {
            this.siteName = siteName;
            return this;
        }

        public Builder drill() {
            drill = true;
            return this;
        }

        public Builder remotePath(String remotePath) {
            this.remotePath = remotePath;
            return this;
        }

        public Builder localPath(String localPath) {
            this.localPath = localPath;
            return this;
        }

        public Builder user(String user) {
            this.user = user;
            return this;
        }

        public Builder password(String password) {
            this.password = password;
            return this;
        }

        /**
         * Set the maximum number of times to re-try connecting.
         * @param maxRetries Default is 10.
         * @return
         */
        public Builder maxRetries(int maxRetries) {
            this.maxRetries = maxRetries;
            return this;
        }

        /**
         * Default behavior is to overwrite existing files.
         * Call this method to cause existing files to be skipped.
         * @return
         */
        public Builder skipExisting() {
            this.skipExisting = true;
            return this;
        }

        public Builder verbose() {
            return this;
        }
    }

    public void setLocalPath(String localPath) {
        this.localPath = localPath;
    }

    protected void info(String message) {
        dspmsg(message);
    }

    protected void error(Throwable t) {
        log(t);
    }

}
